#include "json_builder.h"
#include "frontend.h"
#include "thirdparty/jsoncpp/include/json/writer.h"
#include "thirdparty/jsoncpp/include/json/value.h"
#include "parser.h"

TypeOption CppTypeOptions = {
     {"seq", "std::vector"},
     {"dict", "std::unordered_map"},
     {"set", "std::unordered_set"},
     {"i8", "std::int8_t"},
     {"i16", "std::int16_t"},
     {"i32", "std::int32_t"},
     {"i64", "std::int64_t"},
     {"ui8", "std::uint8_t"},
     {"ui16", "std::uint16_t"},
     {"ui32", "std::uint32_t"},
     {"ui64", "std::uint64_t"},
     {"string", "std::string"},
     {"bool", "bool"},
     {"float", "float"},
     {"double", "double"},
     {"void", "void"},
};

TypeOption CSharpTypeOptions = {
     {"seq", "List"},
     {"dict", "Dictionary"},
     {"set", "HashSet"},
     {"i8", "sbyte"},
     {"i16", "short"},
     {"i32", "int"},
     {"i64", "long"},
     {"ui8", "byte"},
     {"ui16", "ushort"},
     {"ui32", "uint"},
     {"ui64", "ulong"},
     {"string", "string"},
     {"bool", "bool"},
     {"float", "float"},
     {"double", "double"},
     {"void", "void"},
};


TypeOption ProtobufTypeOptions = {
    {"seq", "repeated"},
    {"dict", "map"},
    {"set", "repeated"},
    {"i8", "int32"},
    {"i16", "int32"},
    {"i32", "int32"},
    {"i64", "int64"},
    {"ui8", "uint32"},
    {"ui16", "uint32"},
    {"ui32", "uint32"},
    {"ui64", "uint64"},
    {"string", "string"},
    {"bool", "bool"},
    {"float", "float"},
    {"double", "double"},
    {"void", ""},
};

TypeOption GolangTypeOptions = {
    {"seq", "[]"},
    {"dict", "map"},
    {"set", "map"},
    {"i8", "int8"},
    {"i16", "int16"},
    {"i32", "int32"},
    {"i64", "int64"},
    {"ui8", "uint8"},
    {"ui16", "uint16"},
    {"ui32", "uint32"},
    {"ui64", "uint64"},
    {"string", "string"},
    {"bool", "bool"},
    {"float", "float32"},
    {"double", "float64"},
    {"void", "void"},
};

JsonBuilder::JsonBuilder(Frontend* frontend) : frontend_(frontend) {
}

JsonBuilder::~JsonBuilder() {
}

std::string getTypeString(ValueType type, const TypeOption& typeOptions) {
    switch (type) {
        case ValueType::I8:
            return typeOptions.at("i8");
        case ValueType::I16:
            return typeOptions.at("i16");
        case ValueType::I32:
            return typeOptions.at("i32");
        case ValueType::I64:
            return typeOptions.at("i64");
        case ValueType::UI8:
            return typeOptions.at("ui8");
        case ValueType::UI16:
            return typeOptions.at("ui16");
        case ValueType::UI32:
            return typeOptions.at("ui32");
        case ValueType::UI64:
            return typeOptions.at("ui64");
        case ValueType::BOOL:
            return typeOptions.at("bool");
        case ValueType::FLOAT:
            return typeOptions.at("float");
        case ValueType::DOUBLE:
            return typeOptions.at("double");
        case ValueType::STRING:
            return typeOptions.at("string");
        case ValueType::VOID:
            return typeOptions.at("void");
        default:
            break;
    }
    return std::to_string((std::underlying_type<ValueType>::type)type);
}

std::string getIdlTypeString(ValueType type) {
    switch (type) {
        case ValueType::I8:
            return "i8";
        case ValueType::I16:
            return "i16";
        case ValueType::I32:
            return "i32";
        case ValueType::I64:
            return "i64";
        case ValueType::UI8:
            return "ui8";
        case ValueType::UI16:
            return "ui16";
        case ValueType::UI32:
            return "ui32";
        case ValueType::UI64:
            return "ui64";
        case ValueType::BOOL:
            return "bool";
        case ValueType::FLOAT:
            return "float";
        case ValueType::DOUBLE:
            return "double";
        case ValueType::STRING:
            return "string";
        case ValueType::VOID:
            return "void";
        default:
            break;
    }
    return std::to_string((std::underlying_type<ValueType>::type)type);
}

void buildNode(const IdlNode& node, Json::Value& field,
    const TypeOption& typeOptions, bool withName) {
    switch (node.type) {
        case ValueType::DICT: {
                field["type"] = typeOptions.at("dict");
                Json::Value jsonKey;
                jsonKey["type"] = getTypeString(node.first->type, typeOptions);
                jsonKey["IdlType"] = getIdlTypeString(node.first->type);
                field["key"] = jsonKey;
                Json::Value jsonValue;
                if (node.second->type == ValueType::STRUCT) {
                    jsonValue["isStruct"] = true;
                    jsonValue["type"] = node.second->token.getText();
                    jsonValue["IdlType"] = "struct";
                } else {
                    jsonValue["type"] = getTypeString(node.second->type,
                        typeOptions);
                    jsonValue["IdlType"] = getIdlTypeString(node.second->type);
                }
                field["value"] = jsonValue;
                field["IdlType"] = "dict";
                if (withName) {
                    field["name"] = node.token.getText();
                }
            }
            break;
        case ValueType::SEQ: {
                field["type"] = typeOptions.at("seq");
                Json::Value jsonKey;
                if (node.first->type == ValueType::STRUCT) {
                    jsonKey["isStruct"] = true;
                    jsonKey["type"] = node.first->token.getText();
                    jsonKey["IdlType"] = "struct";
                } else {
                    jsonKey["type"] = getTypeString(node.first->type,
                        typeOptions);
                    jsonKey["IdlType"] = getIdlTypeString(node.first->type);
                }
                field["key"] = jsonKey;
                field["IdlType"] = "seq";
                if (withName) {
                    field["name"] = node.token.getText();
                }
            }
            break;
        case ValueType::SET: {
                field["type"] = typeOptions.at("set");
                Json::Value jsonKey;
                if (node.first->type == ValueType::STRUCT) {
                    jsonKey["isStruct"] = true;
                    jsonKey["type"] = node.first->token.getText();
                } else {
                    jsonKey["type"] = getTypeString(node.first->type,
                        typeOptions);
                }
                jsonKey["IdlType"] = getIdlTypeString(node.first->type);
                field["key"] = jsonKey;
                field["IdlType"] = "set";
                if (withName) {
                    field["name"] = node.token.getText();
                }
            }
            break;
        case ValueType::STRUCT: {
                field["isStruct"] = true;
                field["type"] = node.token.getText();
                field["IdlType"] = "struct";
                if (withName) {
                    field["name"] = node.first->token.getText();
                }
            }
            break;
        default: {
                field["type"] = getTypeString(node.type, typeOptions);
                field["IdlType"] = getIdlTypeString(node.type);
                if (withName) {
                    field["name"] = node.token.getText();
                }
            }
            break;
    }
}

void buildServiceMethod(const IdlMethod& method, Json::Value& jsonMethod,
    const TypeOption& typeOptions) {
    jsonMethod["name"] = method.name;
    Json::Value jsonRet;
    buildNode(method.retType, jsonRet, typeOptions, false);
    jsonMethod["retType"] = jsonRet;
    if (!method.oneway) {
        jsonMethod["timeout"] = method.timeout;
        if (method.retry) {
            jsonMethod["retry"] = method.retry;
        }
    }
    jsonMethod["noexcept"] = method.no_except ? true : false;
    int index = 1;
    for (auto& arg : method.arg_list) {
        Json::Value jsonArg;
        jsonArg["index"] = index++; 
        buildNode(arg, jsonArg, typeOptions, false);
        jsonMethod["arguments"].append(jsonArg);
    }
    if (method.arg_list.empty()) {
        Json::Value jsonArg;
        jsonArg["index"] = 1;
        jsonArg["type"] = getTypeString(ValueType::VOID, typeOptions);
        jsonArg["IdlType"] = getIdlTypeString(ValueType::VOID);
        jsonMethod["arguments"].append(jsonArg);
    }
}

bool JsonBuilder::build(const Parser& parser, std::ostream& os,
    const TypeOption& typeOptions) {
    Json::Value root;
    Json::Value structNames;
    for (auto& stru : parser.getStructList()) {
        structNames.append(Json::Value(stru.name));
    }
    root["structNames"] = structNames;
    Json::Value serviceNames;
    for (auto& service : parser.getService()) {
        serviceNames.append(Json::Value(service.first));
    }
    root["serviceNames"] = serviceNames;
    Json::Value structs;
    for (auto& stru : parser.getStructList()) {
        Json::Value jsonStruct;
        jsonStruct["name"] = stru.name;
        int index = 1;
        for (auto& field : stru.field_list) {
            Json::Value jsonField;
            jsonField["index"] = index++;
            buildNode(field, jsonField, typeOptions, true);
            jsonStruct["fields"].append(jsonField);
        }
        structs.append(jsonStruct);
    }
    root["structs"] = structs;
    Json::Value services;
    for (auto& service : parser.getService()) {
        Json::Value jsonService;
        jsonService["name"] = service.first;
        if (service.second.loadType == ServiceLoadType::STATIC) {
            jsonService["loadType"] = "static";
        } else {
            jsonService["loadType"] = "dynamic";
        }
        if (service.second.type == ServiceType::MULTIPLE) {
            jsonService["type"] = "multiple";
            if (service.second.maxInst) {
                jsonService["maxInst"] = service.second.maxInst;
            }
        } else if (service.second.type == ServiceType::SINGLE) {
            jsonService["type"] = "single";
        } else if (service.second.type == ServiceType::REENTRANT) {
            jsonService["type"] = "reentrant";
        } else if (service.second.type == ServiceType::GENERIC) {
            jsonService["type"] = "generic";
        } else {
            jsonService["type"] = "single";
        }
        int index = 1;
        for (auto& method : service.second.method_list) {
            Json::Value jsonMethod;
            if (method.oneway) {
                jsonMethod["oneway"] = true;
            }
            jsonMethod["index"] = index++;
            buildServiceMethod(method, jsonMethod, typeOptions);
            jsonService["methods"].append(jsonMethod);
        }
        if (service.second.method_list.empty()) {
            Json::Value value;
            value.resize(0);
            jsonService["methods"] = value;
        }
        for (auto& [name, notation] : service.second.service_notations) {
            Json::Value jsonNotation;
            for (auto& value : notation.values) {
                jsonNotation[name].append(value);
            }
            jsonService["notations"].append(jsonNotation);
        }
        jsonService["uuid"] = service.second.uuid;
        services.append(jsonService);
    }
    root["services"] = services;
    os << root.toStyledString();
    return true;
}
